#include "JsonIoService.h"

// private type declarations

struct JsonIoService::CommandEvent : public QEvent
{
    enum class Command
    {
        Start
        , Stop
    };
    static const QEvent::Type TYPE;
    Command command;
    QStringList arguments;
    CommandEvent(Command command, QStringList arguments);
};

// public functions

JsonIoService::JsonIoService()
{
    connect(
        &_process
        , &QProcess::readyReadStandardOutput
        , this
        , &JsonIoService::_recieve
    );
}

JsonIoService::~JsonIoService()
{
    if (isRunning()) _close();
}

void JsonIoService::setReciever(QObject * reciever)
{
    _reciever = reciever;
}

void JsonIoService::start(
    QString program
    , QStringList program_arguments
)
{
    auto arguments = QStringList(program) << program_arguments;
    QCoreApplication::postEvent(
        this
        , new CommandEvent(
            CommandEvent::Command::Start
            , arguments
        )
        , Qt::HighEventPriority
    );
}

bool JsonIoService::isRunning()
{
    return _process.state() != QProcess::NotRunning;
}

void JsonIoService::stop()
{
    QCoreApplication::postEvent(
        this
        , new CommandEvent(CommandEvent::Command::Stop, {})
        , Qt::HighEventPriority
    );
}

void JsonIoService::write(QJsonDocument message)
{
    QCoreApplication::postEvent(
        this
        , new MessageEvent({message})
        , Qt::HighEventPriority
    );
}

bool JsonIoService::event(QEvent * event)
{
    if (event->type() == MessageEvent::TYPE)
    {
        return _messageEvent(static_cast<MessageEvent *>(event));
    }

    if (event->type() == CommandEvent::TYPE)
    {
        return _commandEvent(static_cast<CommandEvent *>(event));
    }

    return QObject::event(event);
}

const QEvent::Type JsonIoService::MessageEvent::TYPE
    = QEvent::Type(QEvent::registerEventType());

JsonIoService::MessageEvent::MessageEvent(
    QVector<QJsonDocument> messages
)
    : QEvent(TYPE)
    , messages(messages)
{}

// private functions

bool JsonIoService::_messageEvent(MessageEvent * event)
{
    if (!isRunning()) return true;
    for (const auto & message : qAsConst(event->messages))
    {
        _write(message);
    }
    return true;
}

bool JsonIoService::_commandEvent(CommandEvent * event)
{
    if (event->command == CommandEvent::Command::Start)
    {
        auto program = event->arguments.first();
        auto program_arguments = QStringList(
            event->arguments.constBegin() + 1
            , event->arguments.constEnd()
        );
        _start(program, program_arguments);
    }
    else if (
        event->command
        == CommandEvent::Command::Stop
    ) _stop();
    return true;
}

void JsonIoService::_start(
    QString program
    , QStringList program_arguments
)
{
    if (isRunning())
    {
        _close();
    }
    _process.start(
        program
        , program_arguments
    );
    _process.waitForStarted(-1);
}

void JsonIoService::_stop()
{
    _write({});
    _process.closeWriteChannel();
    _process.terminate();
}

void JsonIoService::_close()
{
    _stop();
    if (!_process.waitForFinished(5000)) _process.close();
    _process.waitForFinished(-1);
}

void JsonIoService::_write(QJsonDocument message)
{
    _process.write(message.toJson(QJsonDocument::Compact) + '\n');
}

void JsonIoService::_recieve()
{
    if (!_process.canReadLine()) return;

    QVector<QJsonDocument> messages;
    do {
        messages.append(QJsonDocument::fromJson(_process.readLine()));
    } while (_process.canReadLine());

    if (_reciever == nullptr) return;

    QCoreApplication::postEvent(
        _reciever
        , new MessageEvent(messages)
        , Qt::LowEventPriority
    );
}

// private type definitions

const QEvent::Type JsonIoService::CommandEvent::TYPE
    = QEvent::Type(QEvent::registerEventType());

JsonIoService::CommandEvent::CommandEvent(
    Command command
    , QStringList arguments
)
    : QEvent(TYPE)
    , command(command)
    , arguments(arguments)
{
    Q_ASSERT_X(
        command != Command::Start || !arguments.empty()
        , "JsonIoService::CommandEvent::CommandEvent"
        , "Start command does not have executable path."
    );
}
